package com.zte.claa.inficombo.csa.app.service.nbiot.impl;


import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.zte.claa.inficombo.csa.app.config.LocOptConfProps;
import com.zte.claa.inficombo.csa.app.model.dev.ClaaDevInitState;
import com.zte.claa.inficombo.csa.app.model.dev.DevBase;
import com.zte.claa.inficombo.csa.app.model.dev.LocLoRaBeacon;
import com.zte.claa.inficombo.csa.app.model.redis.AlarmData;
import com.zte.claa.inficombo.csa.app.model.redis.DevMeteData;
import com.zte.claa.inficombo.csa.app.model.redis.LocMessageBean;
import com.zte.claa.inficombo.csa.app.service.csa.impl.BaseCacheManager;
import com.zte.claa.inficombo.csa.app.service.custparam.LocCustParamModelService;
import com.zte.claa.inficombo.csa.app.service.custparam.impl.CoordinateConverter;
import com.zte.claa.inficombo.csa.app.service.custparam.impl.CoordinateInfo;
import com.zte.claa.inficombo.csa.app.service.dev.DevBaseService;
import com.zte.claa.inficombo.csa.app.service.nbiot.IotLocUpdataProcessService;
import com.zte.claa.inficombo.csa.app.service.redis.CSAMessagePublisher;
import com.zte.claa.inficombo.csa.app.service.rptparse.LocRawData;
import com.zte.claa.inficombo.csa.app.service.rptparse.ParserContainer;
import com.zte.claa.inficombo.csa.app.util.GPSUtil;
import com.zte.claa.inficombo.csa.app.util.JsonUtils;


/**
 * 处理IOT-定位终端/锚点的定位数据上报。
 * 非LoRa回传网络下，都可以复用该服务（其实lora定位也可以用，只是稳定起见不再重构代码）。
 */

@Service
public class IotLocUpdataProcessServiceImpl implements IotLocUpdataProcessService, InitializingBean, DisposableBean {
	
	private SimpleDateFormat datetimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	// 日志记录.
	private Logger logger = LoggerFactory.getLogger(IotLocUpdataProcessServiceImpl.class);
	
	@Autowired
	private ParserContainer container;
	
	@Autowired
	private BaseCacheManager cacheManager;
	
	@Autowired
	private DevBaseService devBaseService;
	
	// 锚点自身位置移动距离上限（米），超过后会报警.
	@Value("${csa.nb.anchor.locchgthreshold}")
	private int anchorLocChgThreshold = 200;
	
	// 锚点移位越限告警码
	@Value("${csa.nb.anchor.locchgalarmcode}")
	private String anchorLocChgAlarmCode = "1";
	
	// 定位原始数据处理优化.
	@Autowired
	private LocOptConfProps configProp;

	// 消息主题
	private int beaconPubIndex = 0;
	private CSAMessagePublisher[] beaconPublisherArr = null;
	
	private int anchorPubINdex = 0;
	private CSAMessagePublisher[] anchorPublisherArr = null;
	
	//gps坐标数据处理队列
	private LinkedBlockingQueue<List<DevMeteData>> queue = null;	
	
	//原始gps数据队列
	private LinkedBlockingQueue<List<DevMeteData>> originalGpsQueue = null;	
	
	// MEMS解析字段队列.
	private LinkedBlockingQueue<List<DevMeteData>> devMemsDataQueue = null;	
	
	//终端电量信息.
	protected LinkedBlockingQueue<List<DevMeteData>> devPowerDataQueue = null;
	protected boolean devPowerTaskWorkOn = true;
	
	@Override
	public void afterPropertiesSet() throws Exception {
		this.initLocRawDataPublishers();		 
		this.initOriginalGpsSaveDB();
		this.initDevMemsDataSaver();
		this.initDevPowerProcessTask();	
	}
	
	@Override
	public void destroy() throws Exception {
		if(this.originalGpsQueue != null){
			this.originalGpsQueue.clear();
			this.originalGpsQueue = null;
		}
		if(this.devMemsDataQueue != null){
			this.devMemsDataQueue.clear();
			this.devMemsDataQueue = null;
		}
		if(this.devPowerDataQueue != null){
			this.devPowerDataQueue.clear();
			this.devPowerDataQueue = null;
		}
	}
	
	/**
	 * 克拉自研CM-NB定位设备的解析.
	 * @param locData
	 */
	public void parseClaaLocRawData(LocRawData locData, final DevBase dev) {
		logger.info("Begin process NB-claalocrawdata: "+locData);
		try{
			List<String> datas = locData.getGpsJsonDatas();
			if(datas != null && datas.size() > 0){
				this.saveClaaLocRawData(datas, dev, LocRawData.GSP_DATA_TABLE);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].gps failed.", e);
		}
		try{
			List<String> datas = locData.getLoraTerminalJsonDatas();
			if(datas != null && datas.size() > 0){
				this.saveClaaLocRawData(datas, dev, LocRawData.LORA_TERMINAL_DATA_TABLE);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].lora failed.", e);
		}
		try{
			List<String> datas = locData.getAnchorJsonDatas();
			if(datas != null && datas.size() > 0){
				// 尝试发布...
				this.publishAnchorLocRawData(datas, dev);
				// 判断是否需要存库...
				if(this.configProp == null || (this.configProp.isSavetodb() == true)){
					this.saveClaaLocRawData(datas, dev, LocRawData.BT_ANCHOR_DATA_TABLE);
				}
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].bt.anchor failed.", e);
		}
		try{
			List<String> datas = locData.getBeaconJsonDatas();
			if(datas != null && datas.size() > 0){
				// 尝试发布...
				this.publishBeaconLocRawData(datas, dev);
				// 判断是否存库..
				if(this.configProp == null || (this.configProp.isSavetodb() == true)){
					this.saveClaaLocRawData(datas, dev, LocRawData.BT_BEACON_DATA_TABLE);
				}
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].bt.beacon failed.", e);
		}
		try{
			List<String> datas = locData.getGpsDetailJsonData();
			if(datas != null && datas.size() > 0){
				this.saveClaaLocRawData(datas, dev, LocRawData.GPS_DETAIL_TABLE);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].gps.detail failed.", e);
		}
		try{
			List<String> datas = locData.getLoraBeaconData();
			if (datas != null && datas.size() > 0){
				this.saveClaaLocRawData(datas, dev, LocRawData.GSP_DATA_TABLE);
				this.updateDevStatus(datas, dev);//lora信标坐标需要更新到配置表中
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].lora.beacon failed.", e);
		}
		// MEMS字段解析数据处理》	
		try{
			logger.info("Begin process lcs_dev_mems_state: "+locData);
			List<String> datas = locData.getMemsStateJsonData();
			if(datas != null && datas.size() > 0){
				this.parseAndSaveLocMemsStateData(datas, dev);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].gps failed.", e);
		}
		//解析终端电量数据
		try{
			List<String> datas = locData.getDevPowerData();
			if(datas != null && datas.size() > 0){
				this.pushDevPowerData2Queue(datas, dev);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].device power data failed.", e);
		}
		/**
		 * 解析蓝牙信标数据(信标是随终端一起报上来,js插件先将数据包装)
		 * locData.getLabelPowerData() 获取的datas中包含若干
		 * btlabel的电压等级,需要每个单独处理
		 */
		try{
			List<String> datas = locData.getLabelPowerData();			
			if(datas != null && datas.size() > 0){
				for(String data : datas){
					this.processBeaconVoltLevel(data, dev);
				}				
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].bt beacon volt level failed.", e);
		}
		//解析终端与网管信号状态数据
		try{
			List<String> datas = locData.getDeveuiGwStateData();
			if(datas != null && datas.size() > 0){
				this.saveClaaLocRawData(datas, dev, LocRawData.DEVICE_GW_STATE_TABLE);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].device gw state data failed.", e);
		}
		
		//解析uwb定位数据		
		try{
			logger.info("Begin process uwb beacon data: "+locData);
			List<String> datas = locData.getUwbJsonData();
			if(datas != null && datas.size() > 0){
				// 这里直接使用蓝牙定位的主题发布
				this.publishBeaconLocRawData(datas, dev);
			}
		}catch(Exception e){
			logger.error("[parseClaaLocRawData].uwbbeacon failed.", e);
		}
		
		//解析CM-NB锚点自身定位数据		
		try{
			logger.info("Begin process cm-nb anchor self location data: "+locData);
			List<String> datas = locData.getCmNbAnchorGpsData();
			if(datas != null && datas.size() > 0){
				// 对比cm-nb 的anchor自设位置变更是否超限，并更新自身位置.
				this.processCmNbAnchorGpsData(datas, dev);
			}
		}catch(Exception e){
			logger.error("[getCmNbAnchorGpsData].cm-nb anchor loc failed.", e);
		}
		
		// 检查是否是为自研设备的初始化状态数据上报.
		if(locData.hasActivedData()){
			this.saveClaaDevInitState(locData, dev);
		}		
	}
	
	// 定位数据发布器初始化.	
	private void initLocRawDataPublishers(){
		try{
			StringRedisTemplate redisStrTemplate = this.container.getRedisStrTemplate();
			int beaconPubNum = this.configProp.getBeacontopicnum();
			if(beaconPubNum > 0){
				String beaconTopicPrefix = this.configProp.getBeacontopicprefix().trim();
				this.beaconPubIndex = 0; 
				this.beaconPublisherArr = new CSAMessagePublisher[beaconPubNum];
				for(int b=0; b<beaconPubNum; b++){
					this.beaconPublisherArr[b] = new CSAMessagePublisher(redisStrTemplate, beaconTopicPrefix+b);
				}
			}
			int anchorPubNum = this.configProp.getAnchortopicnum();
			if(anchorPubNum > 0){
				String anchorTopicPrefix = this.configProp.getAnchortopicprefix().trim();
				this.anchorPubINdex = 0; 
				this.anchorPublisherArr = new CSAMessagePublisher[anchorPubNum];
				for(int a=0; a<anchorPubNum; a++){
					this.anchorPublisherArr[a] = new CSAMessagePublisher(redisStrTemplate, anchorTopicPrefix+a);
				}
			}
			logger.info("initLocRawDataPublishers ok: beaconPubNum={}, anchorPubNum={}.", beaconPubNum, anchorPubNum);			
		}catch(Exception e){
			logger.error("initLocRawDataPublishers failed!", e);
		}
	}

	//gps原始坐标入库任务线程
	private void initOriginalGpsSaveDB(){		
		this.originalGpsQueue = new LinkedBlockingQueue<List<DevMeteData>>(5000);	
		if(this.configProp.isSaveoriginalgps()){			
			Thread originalGpsSaveThread = new Thread(new Runnable(){
				@Override
				public void run() {
					launchOriginalGpsSaveDB();
				}
			});			
			originalGpsSaveThread.setName("NB-originalGpsSaveThread");
			originalGpsSaveThread.start();	
			logger.info("NB-originalGpsSavethread started: originalGpsSaveThread");
		}		
	}
	
	//初始化mems数据入库线程.
	private void initDevMemsDataSaver(){	
		if(this.configProp.isSavememsstate()){	
			this.devMemsDataQueue = new LinkedBlockingQueue<List<DevMeteData>>(5000);	
			Thread memsSaveThread = new Thread(new Runnable(){
				@Override
				public void run() {
					launchDevMemsSave2DB();
				}
			});
			memsSaveThread.setName("NB-LocDevMemsDataSaver");
			memsSaveThread.start();	
			logger.info("NB-initDevMemsDataSaver started: LocDevMemsDataSaver");
		} else {
			this.devMemsDataQueue = new LinkedBlockingQueue<List<DevMeteData>>(1);	
		}
	}
		
	//原始数据入库任务
	private void launchOriginalGpsSaveDB(){
		List<DevMeteData> dataList = null;
		boolean isBatchSaveOk = true;		
		while(true){			
			try{
				dataList = this.originalGpsQueue.poll(20, TimeUnit.SECONDS);
			}catch (Exception e) {
				logger.error("[0]saveOriginalGps failed:get data from originalGPSQueue error");
			}
							
			if(dataList != null && dataList.size() > 0){						
				try{						
					this.container.getPersisService().saveLocRawDataByBatch(dataList, LocRawData.GSP_DATA_TABLE_TEST);
					isBatchSaveOk = true;
				}catch(Exception e){
					isBatchSaveOk = false;
					logger.error("[1]saveOriginalGps failed: table=" + LocRawData.GSP_DATA_TABLE_TEST, e);
				}
				
				if(isBatchSaveOk == false){																							
					for(DevMeteData meteData : dataList){	
						try{// 防止批量保存时个别异常导致全部入库异常.
							this.container.getPersisService().saveLocRawData(meteData,  LocRawData.GSP_DATA_TABLE_TEST);
						}catch(Exception ex){
							logger.error("[2]saveOriginalGps failed: " +  LocRawData.GSP_DATA_TABLE_TEST + ", " + meteData, ex);
						}
					}						
				}									
			}											
		}
	}
	
	// mems数据入库...
	private void launchDevMemsSave2DB(){
		List<DevMeteData> dataList = null;
		boolean isBatchSaveOk = true;
		while(true){
			try{
				dataList = this.devMemsDataQueue.poll(20, TimeUnit.SECONDS);
			}catch (Throwable t) {
				logger.error("[0]save mems state failed failed: get data from queue error.");
			}	
			if(dataList != null && dataList.size() != 0){						
				try{						
					this.container.getPersisService().saveLocRawDataByBatch(dataList, LocRawData.MEMS_STATE_DATA_TABLE);
					isBatchSaveOk = true;
				}catch(Exception e){
					isBatchSaveOk = false;
					logger.error("[1]save mems state failed: table=" + LocRawData.MEMS_STATE_DATA_TABLE, e);
				}					
				if(isBatchSaveOk == false){																							
					for(DevMeteData meteData : dataList){	
						try{// 防止批量保存时个别异常导致全部入库异常.
							this.container.getPersisService().saveLocRawData(meteData,  LocRawData.MEMS_STATE_DATA_TABLE);
						}catch(Exception ex){
							logger.error("[2]save mems state failed: " +  LocRawData.MEMS_STATE_DATA_TABLE + ", " + meteData, ex);
						}
					}						
				}									
			}											
		}
	}

	/**
	 * 更新lora信标的坐标:正常流程需要缓存与db做对比后判断是否需要更新到db,
	 * 但是lora信标即使安装固定,每次上报的gps都会存在差异,这里使用缓存对比更新意义不大
	 * 而且信标的上报周期一般都是以小时为单位,没有数据库压力,所以这里直接更新db
	 * 
	 * @param jsonDatas
	 * @param dev
	 */
	private void updateDevStatus(List<String> jsonDatas , final DevBase dev){		
		if(jsonDatas == null || jsonDatas.size() == 0) {
			return;
		}
		//jsplugin解析的lora信标数据List只有一个
		String rptData = jsonDatas.get(0);
		Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());
	
		String locX = this.getMapValue(mdata, "lat", "254");
		String locY = this.getMapValue(mdata, "lon", "254");	
		if("254".equals(locX) || "254".equals(locY))//定位失败不更新
			return;
		
		LocLoRaBeacon locLoRaBeacon = new LocLoRaBeacon();		
		locLoRaBeacon.setLocX(Double.parseDouble(locX));
		locLoRaBeacon.setLocY(Double.parseDouble(locY));
		locLoRaBeacon.setDeveui(dev.getDevEui());
		locLoRaBeacon.setProjectId(dev.getProjectId());
					
		try{
			this.container.getLocLoRaBeaconMapper().updateCoordinatePrimaryKey(locLoRaBeacon);
		}catch (Exception e) {
			logger.error("[updateDevStatus].lora.beaconInfo failed.", e);
		}
		try{
			this.container.getLocLoRaBeaconMapper().updateProjectDevCoordinate(locLoRaBeacon);
		}catch (Exception e) {
			logger.error("[updateDevStatus].lora.beaconProject failed.", e);
		}		
	}

	// 保存或更新设备初始化状态.
	private void saveClaaDevInitState(LocRawData locData, final DevBase dev) {
		try{
			ClaaDevInitState state = new ClaaDevInitState();
			state.setConfirmactive(locData.getActived());
			state.setDeveui(dev.getDevEui());
			state.setDevtype(dev.getDevType());
			state.setProjectid(dev.getProjectId());
			this.container.getDevConfDataSenderService().submitClaaDevInitState(state);
			logger.info("SubmitClaaDevInitState ok: "+state); 
		}catch(Exception e){
			logger.error("saveClaaDevInitState failed: "+dev.getDevEui()+", state="+locData.getActived(), e);
		}		
	}
	
	//构造metedata
	private DevMeteData buildDevMeteData(Map<String, Object> mdata, final DevBase dev){
		if(mdata.isEmpty()){
			return null;
		}		
		DevMeteData meteData = new DevMeteData();
		meteData.setAppeui(dev.getAppEui());
		meteData.setDeveui(dev.getDevEui());
		meteData.setDevname(dev.getDevName());
		meteData.setDevtype(dev.getDevType());
		meteData.setPort("0");
		meteData.setProjectid(dev.getProjectId());
		meteData.setMdata(mdata);		
		return meteData;
	}                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
	
	//保存转换后的gps坐标数据
	private void saveConversionGps(List<String> jsonDatas, final DevBase dev){
		List<DevMeteData> dataList = new ArrayList<DevMeteData>();
		
		for (String rptData : jsonDatas) {
			Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());				
			dataList.add(buildDevMeteData(converterCoordinate(mdata,dev), dev));			
		}			
		//需要gps->xyz的坐标,转换后入队列,交给坐标纠偏服务处理
		boolean ret = this.queue.offer(dataList);
		if(!ret){
			logger.warn("add devGpsData to queue failed: " + dataList.get(0).getDeveui());
		}		
	}
		
	//保存转换前的原始gps坐标数据
	private void saveOriginalGps(List<String> jsonDatas, final DevBase dev){
		List<DevMeteData> orgDataList = new ArrayList<DevMeteData>();
		
		for (String rptData : jsonDatas) {
			Map<String, Object> temp = JsonUtils.jsonStrToHashMap(rptData.toString());
			Map<String, Object> orgMdata = new HashMap<String, Object>();
			orgMdata.put("projectid", temp.get("projectid"));
			orgMdata.put("in_out_flag", temp.get("in_out_flag"));
			orgMdata.put("type", temp.get("type"));
			orgMdata.put("lon", temp.get("lon"));
			orgMdata.put("lat", temp.get("lat"));
			orgMdata.put("alt", temp.get("alt"));
			orgMdata.put("cur_volt",temp.get("cur_volt"));					
			orgDataList.add(buildDevMeteData(orgMdata,dev));
		}			
		//将原始gps坐标存入队列
		if(this.configProp.isSaveoriginalgps() && orgDataList != null && orgDataList.size() != 0){
			boolean ret = this.originalGpsQueue.offer(orgDataList);
			if(!ret){
				logger.warn("add originalGpsData to queue failed: " + orgDataList.get(0).getDeveui());
			}
		}	
	}
		
	
   /**
	 * 克拉自研定位设备的解析.
	 * @param locData
	 */
	private void saveClaaLocRawData(List<String> jsonDatas, final DevBase dev, String tabName) {
		 //需要转换且是gps坐标需要直接入gps表
		if(LocRawData.GSP_DATA_TABLE.equalsIgnoreCase(tabName.trim()) && needConverte(dev)){
			//保存转换后的坐标
			saveConversionGps(jsonDatas, dev);			
			//如果开关打开,则保存转换前的gps数据
			if(this.configProp.isSaveoriginalgps()){
				saveOriginalGps(jsonDatas, dev);
			}			
		}else{
			List<DevMeteData> dataList = new ArrayList<DevMeteData>();
			for (String rptData : jsonDatas) {
				Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());			
				dataList.add(buildDevMeteData(mdata,dev));
			}
			saveClaaLocRawData(dataList,tabName);
		}
	}
	
	/**
	 * 重载saveClaaLocRawData,方便gps纠偏时坐标的异步操作
	 */
	private void saveClaaLocRawData(List<DevMeteData> dataList, String tabName) {
		boolean isBatchSaveOk = true;
		// 自研定位设备类型.入库到基础库的lcs相关表中...
		if(!LocRawData.GSP_DATA_TABLE.equalsIgnoreCase(tabName.trim())){
			try{				
				this.container.getPersisService().saveLocRawDataByBatch(dataList, tabName);
				isBatchSaveOk = true;
			}catch(Exception e){
				isBatchSaveOk = false;
				logger.error("[1]saveClaaLocRawData failed: table="+tabName, e);
			}
			// 防止批量保存时个别异常导致全部入库异常.
			if(isBatchSaveOk == false){
				for(DevMeteData meteData : dataList){
					try{								
						this.container.getPersisService().saveLocRawData(meteData, tabName);
						logger.info("[*^_^*]Save claa loc Rawdata message ok: " + JsonUtils.toJson(meteData));
					}catch(Exception ex){				
						logger.error("[2]saveClaaLocRawData failed: " + tabName + ", " + meteData, ex);
						continue;
					}				
				}
			}							
		}
		//gps坐标数据统一在base侧入库,csa不入库	 
		else{		
			for(DevMeteData meteData : dataList){
				// LCS的GPS坐标数据需要广播，其他的原始定位数据不需要广播...
				LocMessageBean locMB = this.transformGpsMeteData(meteData);
				String pubMsg = JsonUtils.toJson(locMB);
				this.container.getClaaLocGpsCoordPublisher().publishMessage(pubMsg);
				logger.info("[*^_^*]Publish claa loc gps coordinate data ok: " + pubMsg);
				// 2020-04-29 配合GPS定位终端静止时参照历史定位结果构造最新一次的定位数据。
				if(locMB.getIn_out_flag() != LocMessageBean.FLAG_INVALID){
					this.container.getPersisService().putMeteDataToCache(meteData);
				}
			}			
		}					
	}	
		
	/**
	 * 克拉自研定位设备的MEMS状态数据解析.
	 * @param locData
	 */
	private void parseAndSaveLocMemsStateData(List<String> jsonDatas, final DevBase dev) {
		if(jsonDatas == null || this.configProp.isSavememsstate() == false){
			return;
		}
		// 将LocRawData的jsondata列表转换为 metedata列表，然后持久化. 
		List<DevMeteData> dataList = new ArrayList<DevMeteData>();	
		for (String rptData : jsonDatas) {
			Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());	
			if(mdata != null){
				mdata.put("projectid", dev.getProjectId());
				mdata.put("devname", String.valueOf(dev.getDevName()));
				mdata.put("collecttime", new Timestamp(System.currentTimeMillis()));
			}
			dataList.add(buildDevMeteData(mdata,dev));
		}
		//将MEMS-state数据存入队列
		if(this.configProp.isSavememsstate() && dataList != null && dataList.size() != 0){
			boolean b = this.devMemsDataQueue.offer(dataList);
			if(!b){
				logger.warn("add locdevmemsstateData to queue failed: " + dataList.get(0).getDeveui());
			}
		}	
	}
	
	//判断是否需要转换
	private Boolean needConverte(final DevBase dev){
		String projectid = dev.getProjectId();
		if(projectid == null || projectid.length() == 0) {
			return false;		
		}
		CoordinateConverter converter = LocCustParamModelService.locCustParamMap.get(projectid);
		//转换器不存在
		if (converter == null){
			logger.info("gps<-->xyz[needConverte] is false");
			return false;			
		}else{
			return true;
		}
	}

	//坐标转换
	private Map<String, Object> converterCoordinate(Map<String, Object> mdata,final DevBase dev){		
		//获取坐标转换器
		try{
			CoordinateConverter converter = LocCustParamModelService.locCustParamMap.get(dev.getProjectId());
			if (converter == null){
				logger.warn("the coverter is null projectid:{}",dev.getProjectId());
				return mdata;
			}
							
			String strLon = (String)mdata.get("lon");
			String strLat = (String)mdata.get("lat");
			
			if(strLon == null || strLon.length() == 0 || strLat == null || strLat.length() == 0){
				return mdata;
			}
			//254是无效定位数据										
			if (strLon.equals("254") || strLat.equals("254")){  				
				return mdata;
			}
			
			logger.info("before converte Coordinate -> dev:{}, lon:{}, lat:{} ",dev.getDevEui(),strLon,strLat);
			
			CoordinateInfo point = new CoordinateInfo();
			point.setLon(Double.parseDouble(strLon));
			point.setLat(Double.parseDouble(strLat));			
			//地图角度可能存在偏移,矫正下
			converter.converterPoint(point);
			
			logger.info("after converte Coordinate -> dev:{}, x:{}, y:{} ",dev.getDevEui(),point.getxLong(),point.getyLong());
						
			mdata.put("lon", Long.toString(point.getxLong()));
			mdata.put("lat", Long.toString(point.getyLong()));
			mdata.put("zone_id", Integer.toString(converter.getZoneid()));
			return mdata;
		}catch (Exception e) {
			logger.info("gps<-->xyz[converterCoordinate] fail : " + e.getMessage());
			return mdata;
		}		
	}
	
	/**
	 * 将定位终端直接上报的GPS坐标信息（metedata）转换为LocMessageBean
	 *
	 * @param meteData
	 * @return
	 */
	private LocMessageBean transformGpsMeteData(final DevMeteData meteData){
		LocMessageBean locMB = new LocMessageBean();
		locMB.setDeveui(meteData.getDeveui());
		locMB.setProjectid(meteData.getProjectid());
		locMB.setReport_time(datetimeFormat.format(meteData.getRpttime()));
		Map<String, Object> mdataMap = meteData.getMdata();
		locMB.setAlt(Double.parseDouble(this.getMapValue(mdataMap, "alt", "0")));
		locMB.setIn_out_flag(Byte.parseByte(this.getMapValue(mdataMap, "in_out_flag", String.valueOf(LocMessageBean.FLAG_OUT))));
		locMB.setLat(Double.parseDouble(this.getMapValue(mdataMap, "lat", "0")));
		locMB.setLon(Double.parseDouble(this.getMapValue(mdataMap, "lon", "0")));
		locMB.setType(Byte.parseByte(this.getMapValue(mdataMap, "type", "34")));
		locMB.setZone_id(Integer.parseInt(this.getMapValue(mdataMap, "zone_id", "0")));
		locMB.setMemsShockFlag(Integer.parseInt(this.getMapValue(mdataMap, "mems_shock_flag", "0")));
		locMB.setxAngle(Float.parseFloat(this.getMapValue(mdataMap, "xAngle", "0")));
		locMB.setyAngle(Float.parseFloat(this.getMapValue(mdataMap, "yAngle", "0")));
		locMB.setzAngle(Float.parseFloat(this.getMapValue(mdataMap, "zAngle", "-90")));	
		
		locMB.setSos(Integer.parseInt(this.getMapValue(mdataMap, "sos", "0")));	
		locMB.setPress(Float.parseFloat(this.getMapValue(mdataMap, "press", "0")));	
		locMB.setTemp(Float.parseFloat(this.getMapValue(mdataMap, "temp", "0")));
		locMB.setHumd(Float.parseFloat(this.getMapValue(mdataMap, "humd", "0")));
		// 增加电压和电量字段
		locMB.setCur_percent(Float.parseFloat(this.getMapValue(mdataMap, "cur_percent", "95")));
		locMB.setCur_volt(Float.parseFloat(this.getMapValue(mdataMap, "cur_volt", "3.5")));
		
		return locMB;
	}
	
	/**
	 * 解析metedata的值
	 * 
	 * @param mdataMap
	 * @param keyName
	 * @param defVal
	 * @return
	 */
	private String getMapValue(Map<String, Object> mdataMap, String keyName, String defVal){
		try{
			Object obj = mdataMap.get(keyName);
			if(obj != null && !obj.toString().equals("")){
				return obj.toString();
			}
		}catch(Exception ignore){}
		return defVal;
	}
	
	// 发布beacon的原始定位数据...
	private void publishBeaconLocRawData(List<String> jsonDatas, final DevBase dev){
		if(beaconPublisherArr == null || beaconPublisherArr.length == 0){
			logger.info("publishBeaconLocRawData: beaconPublisherArr is not init!");
			return;
		}
		for (String rptData : jsonDatas) {
			Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());
			// 选择发布器进行消息发布.
			String pubMsg = JsonUtils.toJson(buildDevMeteData(mdata,dev));
			int p = (this.beaconPubIndex++) % beaconPublisherArr.length;
			this.beaconPublisherArr[p].publishMessage(pubMsg);
			// 每100个复位一次...
			if(this.beaconPubIndex > 1000){
				this.beaconPubIndex = 0;
			}
			logger.info("[*^_^*]publishBeaconLocRawData ok: " + pubMsg);
		}
	}

	// 发布anchor的原始定位数据...
	private void publishAnchorLocRawData(List<String> jsonDatas, final DevBase dev){
		if(anchorPublisherArr == null || anchorPublisherArr.length == 0){
			logger.info("publishAnchorLocRawData: anchorPublisherArr is not init!");
			return;
		}
		for (String rptData : jsonDatas) {
			Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());
			// 选择发布器进行消息发布.
			String pubMsg = JsonUtils.toJson(buildDevMeteData(mdata,dev));
			int p = (this.anchorPubINdex++) % anchorPublisherArr.length;
			this.anchorPublisherArr[p].publishMessage(pubMsg);
			// 每100个复位一次...
			if(this.anchorPubINdex > 1000){
				this.anchorPubINdex = 0;
			}
			logger.info("[*^_^*]publishAnchorLocRawData ok: " + pubMsg);
		}
	}
	
	//设备电量管理任务线程
	private void initDevPowerProcessTask(){		
		this.devPowerDataQueue = new LinkedBlockingQueue<List<DevMeteData>>(10000);	
		this.devPowerTaskWorkOn = true;
		Thread devPowerProcessTask = new Thread(new Runnable() {
			@Override
			public void run() {
				launchDevPowerService();
			}
		});
		devPowerProcessTask.setName("CM-NB-DevPowerProcessTask");
		devPowerProcessTask.start();	
		logger.info("DevPowerProcessTask thread started: DevPowerProcessTask");
	} 
	
	//设备电量管理
	protected void launchDevPowerService(){
		List<DevMeteData> dataList = null;		
		while(this.devPowerTaskWorkOn){			
			try{								
				dataList = this.devPowerDataQueue.poll(20, TimeUnit.SECONDS);			
				if(dataList == null){
					continue;
				} 								
				this.container.getDevPowerDataService().processDevPowerData(dataList);				
				if(dataList.size() > 0){			
					//电量计算完成后入库
					this.container.getPersisService().saveLocRawDataByBatch(dataList, LocRawData.DEVICE_POWER_DATA);
				}						
			}catch (Throwable t) {
				logger.error("Dev power data save failed:{}",t.getMessage());			
			}			
		}
		logger.warn("devPowerService thread exit: " + Thread.currentThread().getName());
	}
	
	//将原始电量信息入队列
	private void pushDevPowerData2Queue(List<String> jsonDatas, final DevBase dev){
		if(jsonDatas == null){
			return;
		}
	
		List<DevMeteData> dataList = new ArrayList<>();	
		for (String rptData : jsonDatas) {
			Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());
			if(mdata == null){
				continue;
			}
			mdata.put("projectid", dev.getProjectId());
			DevMeteData meteData = new DevMeteData();
			meteData.setAppeui(dev.getAppEui());
			meteData.setDeveui(dev.getDevEui());
			meteData.setDevname(dev.getDevName());
			meteData.setDevtype(dev.getDevType());
			meteData.setPort("0");
			meteData.setProjectid(dev.getProjectId());
			meteData.setMdata(mdata);
			dataList.add(meteData);
		}
		//电量原始数据存入队列
		if(dataList != null && dataList.size() > 0){
			boolean b = this.devPowerDataQueue.offer(dataList);
			if(!b){
				logger.warn("Add dev power data to queue failed: " + dataList.get(0).getDeveui());
			}
		}
	}
	
	//包装蓝牙信标的电压等级数据
	protected void processBeaconVoltLevel(String jsonData , DevBase dev){
		if(jsonData == null){
			return;
		}	
		List<DevMeteData> dataList = new ArrayList<>();	
		Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(jsonData.toString());
		//这里使用remove方法后mdata只剩下电压等级,与DB模型对齐
		Object labeleui = mdata.remove("labeleui");
		if(labeleui != null){			
			DevMeteData meteData = new DevMeteData();
			meteData.setAppeui(dev.getAppEui());
			meteData.setDeveui((String)labeleui);
			meteData.setDevtype("claa_btlabel");
			meteData.setPort("0");
			meteData.setProjectid(dev.getProjectId());
			meteData.setMdata(mdata);
			dataList.add(meteData);
		}
		Object uwbBeaconEui = mdata.remove("uwbbeaconeui");
		if(uwbBeaconEui != null){			
			DevMeteData meteData = new DevMeteData();
			meteData.setAppeui(dev.getAppEui());
			meteData.setDeveui((String)uwbBeaconEui);
			meteData.setDevtype("claa_uwbbeacon");
			meteData.setPort("0");
			meteData.setProjectid(dev.getProjectId());
			meteData.setMdata(mdata);
			dataList.add(meteData);
		}	
		//将信标电压等级信息入库
		if(dataList != null && dataList.size() > 0){
			boolean b = this.devPowerDataQueue.offer(dataList);
			if(!b){
				logger.warn("Add bt beacon volt level to queue failed: " + dataList.get(0).getDeveui());
			}
		}
	}
	
	/**
	 * 处理CM-NB 锚点自身位置数据：
	 * 1）对比历史位置是否超限； 
	 * 2）更新自身最新位置;
	 * 
	 * @param jsonDatas
	 * @param dev
	 * 约定：cmnb锚点上报的自身GPS位置：{'deveui': '004a770012340099', 'gpslat': 32.001, 'gpslng': 120.001, 'gpsalt': 10}
	 */
	private void processCmNbAnchorGpsData(List<String> jsonDatas, final DevBase dev) throws Exception {
		for (String rptData : jsonDatas) {
			Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());
			Object latObj = mdata.get("gpslat");
			Object lngObj = mdata.get("gpslng");
			if(latObj == null || lngObj == null){
				continue;
			}
			Object altObj = mdata.get("gpsalt");
			altObj = (altObj == null) ? new Integer(0) : altObj;
			double lat = Double.parseDouble(latObj.toString());
			double lng = Double.parseDouble(lngObj.toString());
			int alt = Integer.parseInt(altObj.toString());
			double orgLat = dev.getGpslat();
			double orgLng = dev.getGpslng();
			double distance = GPSUtil.getDistance(lat, lng, orgLat, orgLng);
			// 距离变更超限.
			if(distance > this.anchorLocChgThreshold){
				logger.warn("Anchor {} location change distance beyond {} meter.", dev.getDevEui(), this.anchorLocChgThreshold);
				AlarmData alarm = this.buildAnchorMoveAlarm(dev, distance, lat, lng);
				this.parseAlarmData(alarm);
				// 更新位置.
				this.devBaseService.updateDevCoordinate(dev.getDevEui(), lat,lng, alt);
				dev.setGpsalt(alt);
				dev.setGpslat(lat);
				dev.setGpslng(lng);
				this.cacheManager.putDev(dev);
			} else {
				logger.info("Anchor {} location no change or change distance < {}", dev.getDevEui(), this.anchorLocChgThreshold);
			}
		}
	}
	
	// 锚点位置移动超限告警.
	private AlarmData buildAnchorMoveAlarm(DevBase dev, double distance, double newLat, double newLng) throws Exception {
		// 构造Alarm
		AlarmData alarm = new AlarmData();
		alarm.setAlarmid("A"+Math.abs(UUID.randomUUID().getLeastSignificantBits()));
		alarm.setAlarmlevel(AlarmData.ALARM_LEVEL_1);
		alarm.setAlarmcode(anchorLocChgAlarmCode);
		alarm.setTypeflag(AlarmData.TPFLAG_ALARM);
		alarm.setTitle(dev.getDevName()+"位置变动超过"+((int)distance)+"米");			
		alarm.setDeveui(dev.getDevEui());
		alarm.setDevtype(dev.getDevType());
		alarm.setProjectid(dev.getProjectId());
		alarm.setDevname(dev.getDevName());			
		alarm.setAlarmtime(new Timestamp(System.currentTimeMillis()));
		alarm.setDescp(dev.getDevName()+"位置变动超过"+((int)distance)+"米");
		alarm.setConfirmstate(AlarmData.CONFIRM_STATE_N);	
		alarm.setRemark("原来坐标["+dev.getGpslat()+", "+dev.getGpslng()+"], 新坐标["+newLat+", "+newLng+"].");
		return alarm;
	}
	
	/**
	 * 发布告警数据。
	 * 注1：如果为告警恢复消息，且告警码alarmcode为"",则需要告警接收方根据当前deveui对应的告警情况作补充设置对应的告警码。
	 * 注2：告警接收端进行告警持久化或更新（根据告警缓存数据进行告警码设置，并保证告警数据的唯一性，CSA可能多实例部署）；
	 * @param alarm
	 */
	private void parseAlarmData(AlarmData alarm){
		logger.debug("Publish alarm: "+alarm.toString());
		String alarmJson = JsonUtils.toJson(alarm);
		this.container.getAlarmPublisher().publishMessage(alarmJson);
		logger.info("[*^_^*]Publish alarm message ok: "+alarmJson);	
	}

}
